﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.IO.Compression;
using System.Net.Sockets;

namespace Pst_Backup_Agent
{
    public partial class FrmBackup : Form
    {
        private struct CopyParameters
        {
            internal List<Utilities.RegistryEntry> FilesToCopy { get; set; }
            internal List<Utilities.RegistryEntry> FilesToNotCopy { get; set; }
            internal string ClientId { get; set; }
            internal bool Compress { get; set; }
            internal bool DontBackupThroughWan { get; set; }
            internal string AdditionalSubnets { get; set; }
            internal string DefaultPath { get; set; }
            internal string ServerName { get; set; }
            internal bool SetExclusiveNTFSPermissions { get; set; }
            internal string AdditionalNTFSReadWrite { get; set; }
            internal string AdditionalNTFSFullcontrol { get; set; }
        }

        private List<FileMessage> messages = new List<FileMessage>();
        private System.Threading.Thread copyThread;
        private static volatile bool cancelRequired = false;
        private Utilities logger = new Utilities();

        #region (Private Fields)

        private Bitmap _logo = null;

        #endregion (Private Fields)

        public FrmBackup()
        {
            InitializeComponent();
        }

        internal Bitmap Logo { get { return this._logo; } set { this._logo = value; } }

        private List<Utilities.RegistryEntry> FilesToBackup { get; set; }

        internal void Backup(List<Utilities.RegistryEntry> filesToBackup, List<Utilities.RegistryEntry> filesToNotBackup, ApplicationSettings localSettings)
        {
            logger.IsLogActivated = localSettings.EventLogActivated;
            logger.LogSeverity = localSettings.EventLogSeverity;

            if (this.Logo != null)
            {
                try
                {
                    this.pctBxLogo.Image = this.Logo;
                }
                catch (Exception ex)
                {
                    logger.LogEvent(30000, "Error when setting Logo : " + ex.Message, 0, System.Diagnostics.EventLogEntryType.Error);
                }
            }

            this.FilesToBackup = filesToBackup;

            foreach (Utilities.RegistryEntry file in filesToBackup)
            {
                chkLstFilesToBackup.Items.Add(file.SourcePath + " : " + file.LastSuccessfulBackup.ToShortDateString());
            }
            CopyParameters parameters = new CopyParameters();
            parameters.FilesToCopy = filesToBackup;
            parameters.FilesToNotCopy = filesToNotBackup;
            parameters.ClientId = localSettings.ClientID;
            parameters.Compress = localSettings.FilesAndFoldersCompressFiles;
            parameters.DontBackupThroughWan = localSettings.BackupAgentDontBackupThroughtWan;
            parameters.AdditionalSubnets = localSettings.BackupAgentAdditionalSubnets;
            parameters.DefaultPath = localSettings.FilesAndFoldersDestinationPath;
            parameters.ServerName = localSettings.ReportingServer;
            parameters.SetExclusiveNTFSPermissions = localSettings.BackupAgentSetExclusiveNTFSPermissions;
            parameters.AdditionalNTFSFullcontrol = localSettings.BackupAgentAdditionalNTFSFullcontrol;
            parameters.AdditionalNTFSReadWrite = localSettings.BackupAgentAdditionalNTFSReadWrite;

            copyThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(CopyFiles));
            copyThread.IsBackground = false;
            if (!cancelRequired)
            {
                copyThread.Start(parameters);
            }
        }

        private void CopyFiles(object parameters)
        {
            string destinationPath = String.Empty;
            Stream sourceFile = null;
            Stream outputFile = null;
            try
            {
                FileMessage pingMessage = new FileMessage();
                messages.Add(pingMessage);
                CopyParameters copyParameters = (CopyParameters)parameters;

                foreach (Utilities.RegistryEntry fileToNotBackup in copyParameters.FilesToNotCopy)
                {
                    FileMessage message = new FileMessage();
                    message.File = fileToNotBackup;
                    message.LocalPath = fileToNotBackup.SourcePath;
                    message.RemotePath = fileToNotBackup.DestinationPath;
                    message.Backup = fileToNotBackup.ToBackup;
                    message.ScheduledForBackup = false;
                    message.Result = "Success";
                    message.Size = 0;
                    if (File.Exists(fileToNotBackup.SourcePath))
                    {
                        try
                        {
                            FileInfo pstFile = new FileInfo(fileToNotBackup.SourcePath);
                            message.Size = pstFile.Length;
                        }
                        catch (Exception ex) { logger.LogEvent(30000, ex.Message, 0, System.Diagnostics.EventLogEntryType.Error); }
                    }
                    message.CompressedSize = message.Size;
                    message.Compressed = false;
                    message.StartTime = DateTime.Now.ToUniversalTime();
                    message.EndTime = DateTime.Now.ToUniversalTime();
                    messages.Add(message);
                }

                foreach (Utilities.RegistryEntry fileToBackup in copyParameters.FilesToCopy)
                {
                    FileMessage message = new FileMessage();
                    message.File = fileToBackup;
                    message.LocalPath = fileToBackup.SourcePath;
                    message.RemotePath = !String.IsNullOrEmpty(fileToBackup.DestinationPath) ? fileToBackup.DestinationPath : copyParameters.DefaultPath;
                    message.Backup = true;
                    message.ScheduledForBackup = true;
                    message.Result = "Postponed";
                    message.Size = 0;
                    if (File.Exists(fileToBackup.SourcePath))
                    {
                        try
                        {
                            FileInfo pstFile = new FileInfo(fileToBackup.SourcePath);
                            message.Size = pstFile.Length;
                        }
                        catch (Exception ex) { logger.LogEvent(30000, ex.Message, 0, System.Diagnostics.EventLogEntryType.Error); }
                    }
                    message.CompressedSize = message.Size;
                    message.Compressed = copyParameters.Compress;
                    message.StartTime = DateTime.Now.ToUniversalTime();
                    messages.Add(message);
                }

                this.prgBarOverallProgress.Maximum = copyParameters.FilesToCopy.Count * 100;

                List<Utilities.RegistryEntry> successfullySavedFiles = new List<Utilities.RegistryEntry>();

                int index = 0;
                logger.LogEvent(30000, copyParameters.FilesToCopy.Count.ToString() + " PST files to backup.", 0, System.Diagnostics.EventLogEntryType.Information);
                for (int i = 0; i < messages.Count; i++)
                {
                    if (messages[i].Backup && messages[i].ScheduledForBackup)
                    {
                        if (!copyParameters.DontBackupThroughWan || !Utilities.IsWanLink(messages[i].RemotePath, copyParameters.AdditionalSubnets))
                        {
                            logger.LogEvent(30000, "Starting to treat : " + messages[i].LocalPath, 0, System.Diagnostics.EventLogEntryType.Information);

                            sourceFile = OpenFile(messages[i].LocalPath);
                            if (!String.IsNullOrEmpty(messages[i].RemotePath))
                                destinationPath = Utilities.TranslateDestinationFolder(messages[i].RemotePath);
                            else
                                destinationPath = Utilities.TranslateDestinationFolder(copyParameters.DefaultPath);

                            destinationPath += "\\" + Utilities.GetFilenameFromFullPath(messages[i].LocalPath) + ".tmp";
                            logger.LogEvent(30000, "Destination path is : " + destinationPath, 0, System.Diagnostics.EventLogEntryType.Information);
                            try
                            {
                                System.IO.FileInfo destinationFile = new FileInfo(destinationPath);
                                if (!destinationFile.Directory.Exists)
                                    try
                                    {
                                        Utilities.CreateFolderWithNTFSPermissions(destinationFile.Directory.FullName, copyParameters.SetExclusiveNTFSPermissions, copyParameters.AdditionalNTFSFullcontrol, copyParameters.AdditionalNTFSReadWrite, logger);
                                    }
                                    catch (Exception) { }
                                Utilities.DeleteFile(destinationPath);

                                Action InitAction = () => { UpdateUI(0, -1, false); };
                                this.Invoke(InitAction);

                                bool compress = false;
                                bool copyCompress = false;
                                string tempFolder = String.Empty;

                                if (copyParameters.Compress)
                                {
                                    tempFolder = Utilities.GetTempFolder();
                                    string sourceFilename = (new FileInfo(messages[i].LocalPath)).Name;
                                    Stream compressedFile = new GZipStream(File.Create(tempFolder + sourceFilename + ".gz"), CompressionMode.Compress);
                                    logger.LogEvent(30000, "Starting to Compress : " + messages[i].LocalPath, 0, System.Diagnostics.EventLogEntryType.Information);
                                    this.CopyFile(sourceFile, compressedFile, index, true, false);
                                    sourceFile = new FileStream(tempFolder + sourceFilename + ".gz", FileMode.Open);
                                    compress = true;
                                    copyCompress = true;
                                }
                                if (!cancelRequired)
                                {
                                    outputFile = new FileStream(destinationPath, FileMode.Create);
                                    logger.LogEvent(30000, "Starting to copy to final destination : " + messages[i].LocalPath, 0, System.Diagnostics.EventLogEntryType.Information);
                                    this.CopyFile(sourceFile, outputFile, index, compress, copyCompress);
                                }

                                if (copyParameters.Compress)
                                    Utilities.DeleteFolder(tempFolder);
                                messages[i].EndTime = DateTime.Now.ToUniversalTime();
                                System.Threading.Thread.Sleep(1000);

                                if (!cancelRequired)
                                {
                                    logger.LogEvent(15, messages[i].LocalPath + " has been successfully saved", 1, System.Diagnostics.EventLogEntryType.Information);
                                    FileInfo fileToRename = new FileInfo(destinationPath);
                                    if (fileToRename.Exists)
                                    {
                                        string newFile = Path.ChangeExtension(destinationPath, "");
                                        if (copyParameters.Compress)
                                            newFile = Path.ChangeExtension(destinationPath, ".gz");

                                        Utilities.DeleteFile(newFile);
                                        File.Move(destinationPath, newFile);

                                        messages[i].Result = "Success";
                                        FileInfo backupFile = new FileInfo(newFile);
                                        messages[i].CompressedSize = backupFile.Length;
                                    }

                                    destinationPath = String.Empty;
                                    UpdateRegistry(messages[i].File);

                                    Action nextFile = () => { UpdateUI(100, index, true); };
                                    this.Invoke(nextFile);
                                    System.Threading.Thread.Sleep(1000);
                                    index++;
                                }
                                else
                                {
                                    break;
                                }
                            }
                            catch (Exception ex)
                            {
                                logger.LogEvent(30000, "An Error occurs while copying " + messages[i].LocalPath + " to " + messages[i].RemotePath + ".\r\n" + ex.Message, 0, System.Diagnostics.EventLogEntryType.Error);
                                messages[i].Result = "Failed : " + ex.Message;
                            }
                            finally
                            {
                                CloseFiles(sourceFile, outputFile);
                                messages[i].EndTime = DateTime.Now.ToUniversalTime();
                            }
                        }
                        else
                        {
                            messages[i].EndTime = DateTime.Now;
                            messages[i].Result = "Postponed due to Wan Policy";
                            logger.LogEvent(30000, messages[i].LocalPath + " will not be save due to Wan Policy.", 0, System.Diagnostics.EventLogEntryType.Information);
                        }
                    }
                }
                this.Hide();
                btnCancel.Enabled = false;
            }
            catch (Exception ex) { logger.LogEvent(30000, ex.Message, 0, System.Diagnostics.EventLogEntryType.Error); }

            if (cancelRequired)
            {
                AbortCopy(destinationPath, sourceFile, outputFile);
            }

            CopyParameters param = (CopyParameters)parameters;
            if (!String.IsNullOrEmpty(param.ServerName))
            {
                System.Threading.Thread t = new System.Threading.Thread(this.SendMessagesToServer);
                t.IsBackground = false;
                t.Start(param);
            }
            if (this.chkBxShutdownOnceFinished.Checked)
            {
                FrmShutdownComputer _frmShutdown = new FrmShutdownComputer(logger);
                _frmShutdown.ShowDialog();
            }
            string folderToClear = Utilities.GetTempFolder();
            Utilities.DeleteFolder((new DirectoryInfo(folderToClear)).Parent.FullName);

            this.DialogResult = System.Windows.Forms.DialogResult.OK;
        }

        private void CopyFile(Stream sourceFile, Stream outputFile, int index, bool compress, bool copyCompressed)
        {
            int bufferLength = 1024 * 1024;
            byte[] buffer = new byte[bufferLength];
            long sourceLength = sourceFile.Length;
            long totalReadBytes = 0;
            int readBytes = 0;
            double percentage = 0;
            while ((readBytes = sourceFile.Read(buffer, 0, bufferLength)) > 0)
            {
                totalReadBytes += readBytes;
                percentage = (double)totalReadBytes * 100.0 / sourceLength;
                if (compress)
                    percentage /= 2;
                if (copyCompressed)
                    percentage += 50;

                outputFile.Write(buffer, 0, readBytes);

                Action currentFile = () => { UpdateUI(percentage, index, false); };
                this.Invoke(currentFile);
                if (cancelRequired)
                {
                    break;
                }
            }
            outputFile.Flush();
            CloseFiles(sourceFile, outputFile);
        }

        private void SendMessagesToServer(object parameters)
        {
            CopyParameters param = (CopyParameters)parameters;
            DirectoryInfo destinationFolder = new DirectoryInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Pst Backup 2013\Messages"));

            logger.LogEvent(30000, messages.Count + " message to send to the report server.", 0, System.Diagnostics.EventLogEntryType.Information);
            this.CleanStagingFolder(destinationFolder);

            this.SaveMessage(messages, param.ClientId, destinationFolder);

            try
            {
                string serverName = param.ServerName.Substring(0, param.ServerName.IndexOf(':'));
                int serverPort;

                logger.LogEvent(30000, "Server Name is " + serverName, 0, System.Diagnostics.EventLogEntryType.Information);
                if (int.TryParse(param.ServerName.Substring(param.ServerName.IndexOf(':') + 1, param.ServerName.Length - serverName.Length - 1), out serverPort))
                {
                    logger.LogEvent(30000, "Server port is : " + serverPort.ToString(), 0, System.Diagnostics.EventLogEntryType.Information);
                    Client tcpClient = new Client(serverName, serverPort);
                    tcpClient.Connect();
                    foreach (FileInfo file in destinationFolder.GetFiles())
                    {
                        StreamReader reader = file.OpenText();
                        string messageToSend = reader.ReadToEnd();
                        reader.Close();
                        tcpClient.SendMessage(messageToSend);
                        file.Delete();
                    }
                    tcpClient.Disconnect();
                }
                else
                {
                    logger.LogEvent(20013, "Unable to parse ServerName and ServerPort. " + param.ServerName, 3, System.Diagnostics.EventLogEntryType.Error);
                }
            }
            catch (SocketException socketEx)
            {
                logger.LogEvent(20014, "An error occurs while sending informations to the server. " + socketEx.Message + ". (" + socketEx.SocketErrorCode + ")", 3, System.Diagnostics.EventLogEntryType.Error);
            }
            catch (Exception ex)
            {
                logger.LogEvent(20014, "An error occurs while sending informations to the server. " + ex.Message, 3, System.Diagnostics.EventLogEntryType.Error);
            }
        }

        private void CleanStagingFolder(DirectoryInfo stagingFolder)
        {
            if (stagingFolder.Exists && this.GetFolderSize(stagingFolder) >= (10 * 1024 * 1024))
            {
                logger.LogEvent(30000, "Staging folder needs to be clean.", 0, System.Diagnostics.EventLogEntryType.Information);

                this.CleanFolder(stagingFolder);
            }

        }

        private long GetFolderSize(DirectoryInfo folder)
        {
            long result = 0;

            try
            {
                if (folder.Exists)
                {
                    foreach (System.IO.FileInfo file in folder.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
                    {
                        result += file.Length;
                    }
                }
            }
            catch (Exception) { }

            logger.LogEvent(30000, "Staging folder size is : " + result.ToString(), 0, System.Diagnostics.EventLogEntryType.Information);
            return result;
        }

        private void CleanFolder(System.IO.DirectoryInfo folderToClean)
        {
            try
            {
                List<System.IO.FileInfo> files = new List<FileInfo>();
                foreach (FileInfo file in folderToClean.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
                {
                    files.Add(file);
                }
                System.Comparison<FileInfo> comparison = new Comparison<FileInfo>(this.FileComparer);
                files.Sort(comparison);
                if (files.Count != 1)
                {
                    for (int i = 0; i < files.Count / 2; i++)
                    {
                        try
                        {
                            logger.LogEvent(30000, "Deleting file " + files[i].Name, 0, System.Diagnostics.EventLogEntryType.Information);
                            files[i].Delete();
                        }
                        catch (Exception ex)
                        {
                            logger.LogEvent(30000, "Unable to delete the file : " + ex.Message, 0, System.Diagnostics.EventLogEntryType.Error);
                        }
                    }
                }
                else
                {
                    try
                    {
                        logger.LogEvent(30000, "Deleting file " + files[0].Name, 0, System.Diagnostics.EventLogEntryType.Information);
                        files[0].Delete();
                    }
                    catch (Exception ex)
                    {
                        logger.LogEvent(30000, "Unable to delete the file : " + ex.Message, 0, System.Diagnostics.EventLogEntryType.Error);
                    }
                }
            }
            catch (Exception) { }
        }

        private int FileComparer(FileInfo file1, FileInfo file2)
        {
            return DateTime.Compare(file1.CreationTime, file2.CreationTime);
        }

        private void SaveMessage(List<FileMessage> messagesToSave, string clientID, DirectoryInfo destinationFolder)
        {
            try
            {
                if (!destinationFolder.Exists)
                {
                    destinationFolder.Create();
                }

                string messageID = Guid.NewGuid().ToString();
                Version clientVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;

                FileInfo file = new FileInfo(Path.Combine(destinationFolder.FullName, messageID + ".xml"));
                string username = Utilities.GetWindowsLogon();
                string computerName = Utilities.GetComputerName();

                using (StreamWriter writer = new StreamWriter(file.FullName, false))
                {
                    writer.WriteLine("<Message id=\"" + messageID + "\">");
                    writer.WriteLine("<Client id=\"" + clientID + "\" ClientVersion=\"" + clientVersion.ToString() + "\" Date=\"" + DateTime.Now.ToUniversalTime().ToString() + "\"/>");
                    writer.WriteLine("<ComputerName>" + computerName + "</ComputerName>");
                    writer.WriteLine("<UserName>" + username + "</UserName>");
                    foreach (FileMessage messageToSave in messages)
                    {
                        writer.WriteLine("<File>");
                        writer.WriteLine("<LocalPath>" + messageToSave.LocalPath + "</LocalPath>");
                        writer.WriteLine("<RemotePath>" + messageToSave.RemotePath + "</RemotePath>");
                        writer.WriteLine("<Backup>" + messageToSave.Backup.ToString() + "</Backup>");
                        writer.WriteLine("<ScheduledForBackup>" + messageToSave.ScheduledForBackup.ToString() + "</ScheduledForBackup>");
                        writer.WriteLine("<Result>" + messageToSave.Result + "</Result>");
                        writer.WriteLine("<Size>" + messageToSave.Size.ToString() + "</Size>");
                        writer.WriteLine("<CompressedSize>" + messageToSave.CompressedSize.ToString() + "</CompressedSize>");
                        writer.WriteLine("<Compressed>" + messageToSave.Compressed.ToString() + "</Compressed>");
                        writer.WriteLine("<StartTime>" + messageToSave.StartTime.ToUniversalTime().ToString() + "</StartTime>");
                        writer.WriteLine("<EndTime>" + messageToSave.EndTime.ToUniversalTime().ToString() + "</EndTime>");
                        writer.WriteLine("<ErrorMessage>" + messageToSave.ErrorMessage + "</ErrorMessage>");
                        writer.WriteLine("</File>");
                    }
                    writer.WriteLine("</Message>");
                    writer.Flush();
                    writer.Close();
                }
            }
            catch (Exception ex)
            {
                logger.LogEvent(20011, "An error occurs while saving a message for the server, before it can be send. " + ex.Message, 3, System.Diagnostics.EventLogEntryType.Error);
            }
        }

        private FileStream OpenFile(string filePath)
        {
            FileStream fileStream = null;
            for (int i = 0; i < 5; i++)
            {
                try
                {
                    logger.LogEvent(30000, "Trying to open " + filePath + " for backuping.", 0, System.Diagnostics.EventLogEntryType.Information);
                    fileStream = new FileStream(filePath, System.IO.FileMode.Open, FileAccess.Read);
                    break;
                }
                catch (Exception ex)
                {
                    logger.LogEvent(30000, "Can't open file : " + filePath + ". " + ex.Message, 0, System.Diagnostics.EventLogEntryType.Information);
                    System.Threading.Thread.Sleep(1000);
                }
            }
            return fileStream;
        }

        private void UpdateUI(double percentageCurrentFile, int valueOverAllProgression, bool updateFileList)
        {
            this.prgBarCurrentFile.Value = System.Math.Min((int)percentageCurrentFile, 100);
            if (valueOverAllProgression != -1)
            {
                this.prgBarOverallProgress.Value = System.Math.Min((int)percentageCurrentFile + valueOverAllProgression * 100, this.prgBarOverallProgress.Maximum);
                if (updateFileList)
                {
                    this.chkLstFilesToBackup.SetItemChecked(valueOverAllProgression, true);
                    this.chkLstFilesToBackup.Items[valueOverAllProgression] = this.FilesToBackup[valueOverAllProgression].SourcePath + " : " + DateTime.Today.ToShortDateString();
                    this.chkLstFilesToBackup.Refresh();
                }
                this.prgBarOverallProgress.Refresh();
            }
            this.prgBarCurrentFile.Refresh();
        }

        private void UpdateRegistry(Utilities.RegistryEntry regEntry)
        {
            try
            {
                logger.LogEvent(30000, "Updating 'LastSuccessfulBackup' value in " + regEntry.FullRegistryPath, 0, System.Diagnostics.EventLogEntryType.Information);
                Microsoft.Win32.Registry.SetValue(regEntry.FullRegistryPath, "LastSuccessfulBackup", DateTime.Now);
            }
            catch (Exception ex) { logger.LogEvent(30000, ex.Message, 0, System.Diagnostics.EventLogEntryType.Error); }
        }

        private void AbortCopy(string currentFile, Stream uncompressedFile, Stream outputFile)
        {
            logger.LogEvent(30000, "Aborting copy.", 0, System.Diagnostics.EventLogEntryType.Information);
            CloseFiles(uncompressedFile, outputFile);
            if (!String.IsNullOrEmpty(currentFile))
                Utilities.DeleteFile(currentFile);
            this.DialogResult = System.Windows.Forms.DialogResult.Abort;
        }

        private void CloseFiles(Stream uncompressedFile, Stream outputFile)
        {
            try
            {
                this.btnCancel.Enabled = false;
                if (uncompressedFile != null)
                    uncompressedFile.Close();
                if (outputFile != null)
                    outputFile.Close();
                this.btnCancel.Enabled = true;
            }
            catch (Exception) { }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            logger.LogEvent(11, "User has press 'Cancel' button.", 1, System.Diagnostics.EventLogEntryType.Information);
            btnCancel.Enabled = false;
            cancelRequired = true;
        }

    }
}
